---
title: Nested Donut
---
import { PropsTable } from '@site/src/components/PropsTable'
import { sample } from '../utils/data.ts'
import { DocWrapper } from '../wrappers'

export const data = Array(100)
  .fill(0)
  .map(() => {
    const group = sample(['A', 'B', 'C'])
    return {
      group,
      subgroup: group + Math.floor(Math.random() * 3),
      value: sample([10, 20, 30]),
    }
  })

export const groupColors = { A: 'red', B: 'green', C: 'blue' }
export const labelAlignments = ['along', 'perpendicular', 'straight']

export const nestedDonutProps = () => ({
  name: 'NestedDonut',
  containerName: 'SingleContainer',
  configKey: 'component',
  data,
  dataType: 'Datum',
  layers: [(d) => d.group, (d) => d.subgroup],
})

# Nested Donut
_Nested Donut_ is a variation of our [_Donut_](./Donut) component that supports hierarchical data. Similar to a traditional _Sunburst_
chart, _Nested Donut_ displays hierarchies through a series of a rings or **layers** containing categorical nodes or **segments**.

## Data
_Nested Donut_ expects an array of generic data records, the properties of which will be used to define the _layers_. Each layer
will contain nodes or _segments_ that represent their corresponding quantity in the data array.

For the following examples, assume data is of type `Datum[]`.

```ts
type Datum = {
  group: string;
  subgroup: string;
};
```

### Defining Layers
The `layers` property accepts an array of string accessor functions based on the `Datum` provided.
The first accessor will define the outermost layer of the chart, the second will provide the next, and so on.
We can provide `layers` with accessors such the `group` property defines parent nodes with `subgroup` children.

<DocWrapper {...nestedDonutProps()} showContext='full' />

#### Layers with missing/undefined data

Now consider data where `value` is optional and only groups `A` and `B` have corresponding value properties.
We see the hierarchical data is as expected, where the `C` group does not have child segments in the inner layer:

```ts
layers = [(d: Datum) => d.group, (d: Datum) => d.value]
```

{/* <DocWrapper
  {...nestedDonutProps()}
  data={data.map((d) => ({ ...d, value: d.group === 'C' ? null : d.value }))}
  layers={[(d) => d.group, (d) => d.value]}
  excludeTabs
/> */}

### Segment Values
By default, segment lengths are assigned based on the number of occurrences within the data array.
For example, consider the following data provided to a single layer:

```ts
const data = ['A', 'A', 'A', 'B', 'B', 'C']
```
We would expect the values of A, B, and C to be proportional to their counts within in the array, i.e. 3, 2, and 1, respectively.
{/* <DocWrapper {...nestedDonutProps()} data={[
  ...Array(3).fill('A'),
  ...Array(2).fill('B'),
  ...Array(1).fill('C'),
  ]}
  layerSettings={{ labelAlignment: 'along' }}
  layers={[d => d]}
  excludeTabs
/> */}

### Custom `value` Accessor
Alternatively, you can override the default way _Nested Donut_ calculates values by providing your own `value` accessor function.
For example, f your data is shaped like:
```ts
const data = [{ label: 'A', value: 3 }, { label: 'B', value: 2 }, { label: 'C', value: 1 }]
```

you can provide `value` with `(d: Datum) => d.value` to achieve the same effect.

## Layer Settings
You can customize the appearance for each individual layer with the `layerSettings` property,
which accepts an accessor that returns the following type based on the layer's depth:

```typescript
type NestedDonutLayerSettings = {
  width: number | string; // The layer's width in pixels or css string to be converted to pixels
  labelAlignment: NestedDonutSegmentLabelAlignment; // Alignment of the layer's segment labels
}
```

### Default Values
By default, each layer has the same `NestedDonutLayerSettings` object with the following values:
```ts
{
  width: 50,
  labelAlignment: NestedDonutSegmentLabelAlignment.Perpendicular;
}
```

### Label Alignment
The `labelAlignment` property specifies how the segment labels will align based on their radial position. The possible values are:
- `NestedDonutSegmentLabelAlignment.Along` - label is rotated with respect to the segment angle
- `NestedDonutSegmentLabelAlignment.Perpendicular` - label is rotated perpendicular to the segment angle (**default**)
- `NestedDonutSegmentLabelAlignment.Straight` - no rotation is applied

<div style={{ display: 'flex', padding: '10px', flexWrap: 'wrap', justifyContent: 'space-evenly' }}>
{['along', 'perpendicular', 'straight'].map((d, i) => <DocWrapper {...nestedDonutProps()} containerProps={{ width: 300 }} layerSettings={{ labelAlignment: d }} excludeTabs centralSubLabel={d}/>)}
</div>

### Variable Layer Settings
To set different layer settings depending on the layer, provide an `NestedDonutLayerSettings` accessor function with a numeric parameter.
The number refers to the layer's index in the provided `layers` property.

```ts
layers = [(d: Datum) => d.group, (d: Datum) => d.subgroup]

// The accessor function that takes a layer index and returns a `NestedDonutLayerSettings` object:
layerSettings = (layer: number) => {
  if (i === 0) {
    return { width: 25 }
  }
}
```

<DocWrapper
  {...nestedDonutProps()}
  showContext='full'
  layers={[(d) => d.group, (d) => d.subgroup]}
  layerSettings={(i) => i === 0 && { width: 25 } }
/>

## Layer Padding

You can provide a constant numeric value to `layerPadding` to set the spacing between each layer in pixels.

<DocWrapper
  {...nestedDonutProps()}
  height={350}
  layerPadding={10}
  layers={[(d) => d.group, (d) => d.subgroup, (d) => d.value]}
/>

## Segments

### Segment Datum

Unlike our non-hierarchical components, the data type used by accessors to customize segments will not resemble the `Datum` type because the segments
are generated based on the collection of `Datum` objects provided.

So for certain properties that customize segments, accessors will deal with the following type:

```ts
type NestedDonutSegmentDatum<Datum> = {
  key: Datum[keyof Datum] // the category based on the current layer
  root: Datum[keyof Datum] // the highest parent node
}

type NestedDonutSegment<Datum> = {
  data: NestedDonutSegmentDatum<Datum>;
  depth: number;
  height: number;
  value: number;
}
```

### Segment Color

As an example, we can provide `color` property with an accessor that returns the color based on the segment's `root`:

```ts
export const groupColors = {
  A: 'red',
  B: 'green',
  C: 'blue',
}
segmentColor = (d: NestedDonutSegment<Datum>) => groupColors[d.data.root]
```

This gives us the appearance of three distinct colors based on the segment's group instead of the default shading:

<DocWrapper
  {...nestedDonutProps()}
  segmentColor={d => groupColors[d.data.root]}
/>

### Segment Labels

You can customize a _segment_'s labels with the following properties: `segmentLabel` and `segmentLabelColor`:

```ts
const segmentColor = '#ddd'
const segmentLabel = (d: Datum) => {
  switch (d.data.key) {
    case 'A':
      return 'Alpha'
    case 'B':
      return 'Beta'
    case 'C':
      return 'Charlie'
      return d.data.key
  }
}
const segmentLabelColor = (d: Datum) => groupColors[d.data.root]
```

<DocWrapper
  {...nestedDonutProps()}
  segmentColor='#ddd'
  segmentLabel={d =>
    d.depth === 1
      ? { A: 'Alpha', B: 'Beta', C: 'Charlie' }[d.data.key]
      : d.data.key
  }
  segmentLabelColor={d => groupColors[d.data.root]}
/>

## Direction

By default, _Nested Donut_ will form layers starting with the outermost layer and work inward towards the center. You can use the `NestedDonutDirection.Outwards` or
`'outwards'` to the `direction` property to get a _Nested Donut_ that resembles a traditional _Sunburst_ diagram:

<DocWrapper {...nestedDonutProps()} direction='outwards' />

## Further configuration

_Nested Donut_ shares the following properties with our traditional _Donut_ chart.
Read Donut's [doc page](./Donut) to learn more.

- `angleRange`
- `centralLabel`
- `centralSubLabel`
- `emptySegmentLabel`
- `showBackground`
- `showEmptySegments`

## CSS Variables

_Nested Donut_ supports the following CSS variables:

```css
/* Undefined by default to allow proper fallback to var(--vis-font-family) */
--vis-nested-donut-font-family: undefined;

/* Background */
--vis-nested-donut-background-color: #e7e9f3

/* Central label */
--vis-nested-donut-central-label-font-size: 16px;
--vis-nested-donut-central-label-font-weight: 600;
--vis-nested-donut-central-label-text-color: #5b5f6d;

/* Central sub-label */
--vis-nested-donut-central-sublabel-font-size: 12px;
--vis-nested-donut-central-sublabel-font-weight: 500;
--vis-nested-donut-central-sublabel-text-color: #5b5f6d;

/* Segments */
--vis-nested-donut-segment-stroke-width: 1px;
--vis-nested-donut-segment-stroke-color: var(--vis-nested-donut-background-color);
--vis-nested-donut-segment-label-text-color-light: #5b5f6d;
--vis-nested-donut-segment-label-text-color-dark: #fff;
--vis-nested-donut-segment-label-font-size: 1em;

/* Dark theme */
--vis-dark-nested-donut-background-color: #18160c;
--vis-dark-nested-donut-central-label-text-color: #fff;
--vis-dark-nested-donut-central-sublabel-text-color: #fff;
```

## Component Props

<PropsTable name='VisNestedDonut' />
